#include "Download.h"
#include <thread>
#include <future>
#include <functional>
#include "HttpClient.h"
#include "assert.h"
#include <fstream>
#include <iostream>
#include "Base64.h"
#include <openssl/evp.h>


std::string GetFileName(const std::string &url)
{
    int npos = url.find_last_of("/");
    if (npos == std::string::npos)
    {
         throw std::runtime_error("error url");
    }
    return url.substr(npos + 1);
    
}

bool CDownload::Down(const std::string &url, int thNum)
{
    assert(thNum > 0);
    //获取文件大小
    auto client = GetClient();
    auto length = client->GetFileSize(url);
    //根据线程数划分块大小
    auto blockSize = length / thNum;
    auto lastBlock = (length % thNum == 0 ? blockSize : blockSize + length % thNum );
    
    //启动多个线程进行下载
    std::vector<std::future<bool>> vecFt;
    std::vector<std::shared_ptr<INetProtocol>> vecDownload;
    for (size_t i = 0; i < thNum; i++)
    {
        auto block = (i == thNum - 1 ? lastBlock : blockSize);
        auto dlClient = GetClient();
        vecDownload.push_back(dlClient);
        vecFt.emplace_back(std::async(std::bind(&INetProtocol::Download, dlClient, url, blockSize * i,  block)));
    }

    //同步等待所有线程完成
    bool isFaild = false;
    for (auto i = 0; i < vecFt.size(); i++)
	{
        if (!vecFt[i].get())
        {
            isFaild = true;
            std::cout << "线程：" << i << "下载失败" << std::endl;
        }       
    }
    if (isFaild)
    {
        return false;
    }
    //合并数据
    auto strFileName = GetFileName(url);
    MergerData(strFileName, vecDownload);

    //校验MD5
   return CheckFile(url, strFileName);
}

void CDownload::MergerData(const std::string &fileName, std::vector<std::shared_ptr<INetProtocol>>& vecDownload)
{
    std::ofstream fout(fileName, std::ios::out | std::ios::binary);
	if (!fout)
	{
        throw std::runtime_error("create local file faild");
	}
    for (auto& item : vecDownload)
    {
        fout << item->GetStream().rdbuf();
    }
    
}

bool CDownload::CheckFile(const std::string &url, const std::string& strLocalFile)
{
    auto client = GetClient();
    auto contextMd5 = client->GetContextMd5(url);
    auto localMd5 = GetContentMd5(strLocalFile);
    std::cout << "context    md5:" << contextMd5 << std::endl;
    std::cout << "local file md5:" << localMd5  << std::endl;
    if (contextMd5 != localMd5)
    {
        std::cout << "md5 校验失败， 文件下载失败" << std::endl;
        return false;
    }
    std::cout << "md5 校验一致， 文件下载成功" << std::endl;
    return true;
}

std::shared_ptr<INetProtocol> CDownload::GetClient()
{
    return std::make_shared<CHttpClient>();
}

//获取本地文件MD5，然后通过Base64编码输出
std::string CDownload::GetContentMd5(const std::string &fileName)
{
    std::ifstream fin(fileName, std::ios::in  | std::ios::binary);
    if (!fin)
    {
        throw std::runtime_error("获取文件MD5失败，文件打开失败");
    }

    EVP_MD_CTX  *mdctx;
    unsigned char *md5_digest;
    unsigned int md5_digest_len = EVP_MD_size(EVP_md5());

    mdctx = EVP_MD_CTX_new();
    EVP_DigestInit_ex(mdctx, EVP_md5(), NULL);

    const int bufSize = 4 * 1024 * 1024;
    char *pszBuf = new char[bufSize];
    while (!fin.eof())
    {
        fin.read(pszBuf, bufSize);
        EVP_DigestUpdate(mdctx, pszBuf, fin.gcount());
    }
    md5_digest = (unsigned char *)OPENSSL_malloc(md5_digest_len);
    EVP_DigestFinal_ex(mdctx, md5_digest, &md5_digest_len);
    EVP_MD_CTX_free(mdctx);
    delete[] pszBuf;
    return CBase64::encode(std::string((char*)md5_digest, md5_digest_len));
}